#include "Graphics/sdlgpu.h"
#include "Graphics/renderer.h"
#include "SDL3_image/SDL_image.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"

using namespace Jin;

struct Sprite {
	glm::vec2 pos;
	glm::vec2 size;
	glm::vec4 rotation;
	glm::vec4 texcoord;
	glm::vec4 color;
};

struct AppContext final {

	enum {
		WIN_WIDTH = 800,
		WIN_HEIGHT = 600,
		WIN_FLAGS = 0,

		SPRITE_COUNT= 1000
	};

	SDLFramework sdlfx;
	SDLWindowUPtr mainWindow;
	GPUDeviceUPtr gpuDevice;
	GPUGraphicsPipelineUPtr graphicsPipeline;
	GPUSamplerUPtr texSampler;
	GPUUploadBufferUPtr spritesUpload;
	GPUStorageBufferUPtr spritesBuffer;
	GPUTextureSPtr spritesTexture;
	glm::mat4 camera{ 1.0f };

	AppContext();
	~AppContext();
	bool Init();
	void Run();
	void Cleanup();

private:
	bool _CreateMainWindow();
	bool _CreateGPUDevice();
	bool _CreateGraphicsPipeline(stringv vertFilename, stringv fragFilename);
	bool _CreateSampler();
	bool _CreateImageTexture(stringv filename);

};

AppContext::AppContext() {
	SDL_LogInfo(0, "SDL version: %d.%d.%d", sdlfx.version.major, sdlfx.version.minor, sdlfx.version.micor);
}

AppContext::~AppContext() {

}

bool AppContext::_CreateMainWindow() {
	mainWindow = std::make_unique<SDLWindow>("testSDLGPU", WIN_WIDTH, WIN_HEIGHT, WIN_FLAGS);
	if (!*mainWindow) {
		SDL_LogError(0, "Create window failed! error: %s", SDL_GetError());
		return false;
	}
	SDL_LogInfo(0, "Create window successed!");
	sdlfx.SetFPS(0);
	sdlfx.onFPS = [this](Uint32 fps) {
		mainWindow->SetTitle(std::format("fps:{}, delta:{}", fps, sdlfx.deltaTime).c_str());
		};
	return true;
}

bool AppContext::_CreateGPUDevice() {
	gpuDevice = std::make_unique<GPUDevice>(*mainWindow);
	if (!*gpuDevice) {
		SDL_LogError(0, "Create GPU Device failed! error: %s", SDL_GetError());
		return false;
	}
	SDL_LogInfo(0, "Create GPUDevice dirver: %s successed!", gpuDevice->GetDeviceDriver().data());
	gpuDevice->SetSwapchainParameters(SDL_GPU_SWAPCHAINCOMPOSITION_SDR, SDL_GPU_PRESENTMODE_IMMEDIATE);
	return true;
}

bool AppContext::_CreateGraphicsPipeline(stringv vertFilename, stringv fragFilename) {
	gpuDevice->onLoadShaderCode = [vertFilename, fragFilename](std::vector<Uint8>& vsCode, std::vector<Uint8>& fsCode, stringv driver) {
		SDLFileStorage storage("shaders");
		if (!storage) {
			SDL_LogError(0, "open shaders storage failed! %s", SDL_GetError());
			return false;
		}
		if (!storage.ReadFile(vertFilename, vsCode)) {
			SDL_LogError(0, "Read file: %s failed! %s", vertFilename.data(), SDL_GetError());
			return false;
		}
		if (!storage.ReadFile(fragFilename, fsCode)) {
			SDL_LogError(0, "Read file: %s failed! %s", fragFilename.data(), SDL_GetError());
			return false;
		}
		return true;
		};
	gpuDevice->onSetShaderCreateInfo = [](GPUShaderCreateInfo& vsInfo, GPUShaderCreateInfo& fsInfo, stringv driver) {
		vsInfo.num_uniform_buffers = 1;
		vsInfo.num_storage_buffers = 1;
		fsInfo.num_samplers = 1;
		};
	gpuDevice->onSetColorTargetDescriptions = [](GPUColorTargetDescription*& descs, Uint32& num) {
		descs[0].blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
		descs[0].blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
		descs[0].blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
		descs[0].blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
		num = 1;
		};
	graphicsPipeline = gpuDevice->CreateGraphicsPipeline();
	if (!graphicsPipeline) {
		SDL_LogError(0, "Creaet GraphicsPipeline failed! error: %s", SDL_GetError());
		return false;
	}
	SDL_LogInfo(0, "Create GraphicsPipeline successed!");
	return true;
}

bool AppContext::_CreateSampler() {
	gpuDevice->onSetSamplerCreateInfo = [](GPUSamplerCreateInfo& createInfo) {
		createInfo.min_filter = SDL_GPU_FILTER_NEAREST;
		createInfo.mag_filter = SDL_GPU_FILTER_NEAREST;
		createInfo.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
		};
	texSampler = gpuDevice->CreateSampler();
	if (!texSampler) {
		SDL_LogError(0, "Create Sampler failed! error: %s", SDL_GetError());
		return false;
	}
	SDL_LogInfo(0, "Create Sampler successed!");
	return true;
}

bool AppContext::_CreateImageTexture(stringv filename) {
	spritesTexture = gpuDevice->CreateImageTexture2D("images/" + string{ filename });
	if (!spritesTexture) {
		SDL_LogError(0, "Create Image Texture failed! error: %s", SDL_GetError());
		return false;
	}
	SDL_LogInfo(0, "Create Image Texture successed!");
	return true;
}

bool AppContext::Init() {
	if (!_CreateMainWindow()) {
		return false;
	}
	if (!_CreateGPUDevice()) {
		return false;
	}
	if (!_CreateGraphicsPipeline("shader6.vert.spv", "shader6.frag.spv")) {
		return false;
	}
	if (!_CreateSampler()) {
		return false;
	}
	if (!_CreateImageTexture("ravioli_atlas.bmp")) {
		return false;
	}

	spritesUpload = std::make_unique<GPUUploadBuffer>(*gpuDevice, Uint32(SPRITE_COUNT * sizeof(Sprite)));
	if (!*spritesUpload) {
		return false;
	}

	spritesBuffer = std::make_unique<GPUStorageBuffer>(*gpuDevice, Uint32(SPRITE_COUNT * sizeof(Sprite)));
	if (!*spritesBuffer) {
		return false;
	}

	SDL_srand(0);

	camera = glm::ortho(0.0f, (float)WIN_WIDTH, (float)WIN_HEIGHT, 0.0f, 0.0f, -1.0f);

	return true;
}

void AppContext::Run() {
	static const glm::vec2 texcoords[4]{
		{ 0.0f, 0.0f },
		{ 0.5f, 0.0f },
		{ 0.0f, 0.5f },
		{ 0.5f, 0.5f }
	};

	sdlfx.onFrame = [this]() {
		if (mainWindow->GetWindowFlags() & SDL_WINDOW_MINIMIZED) {
			return;
		}
		GPUCommandBuffer cmd{ *gpuDevice };
		if (!cmd) {
			return;
		}
		GPUSwapchainTexture swapchainTexture{ cmd, *mainWindow };
		if (!swapchainTexture) {
			return;
		}

		cmd.PushVertexUniformData(0, &camera, sizeof(camera));

		{
			GPUTransferBufferMapperT<Sprite*> mapper{ *spritesUpload, true };
			Sprite* sprite = mapper;
			for (Uint32 i = 0; i < SPRITE_COUNT; ++i) {
				Sint32 index = SDL_rand(4);
				sprite[i].pos = { (float)(SDL_rand(WIN_WIDTH)), (float)(SDL_rand(WIN_HEIGHT)) };
				sprite[i].size = { 32, 32 };
				sprite[i].rotation.s = SDL_randf() * SDL_PI_F * 2;
				sprite[i].texcoord = { texcoords[index].x, texcoords[index].y, 0.5f, 0.5f };
				sprite[i].color = { 1.0f, 1.0f, 1.0f, 1.0f };
			}

			GPUCopyPass{ cmd }.UploadToGPUBuffer({ *spritesUpload, 0 }, { *spritesBuffer, 0, SPRITE_COUNT * sizeof(Sprite) }, true);
		}

		GPUColorTargetInfo colorTargetInfo{};
		colorTargetInfo.texture = swapchainTexture;
		colorTargetInfo.clear_color = { 0.2f, 0.3f, 0.3f, 1.0f };
		colorTargetInfo.load_op = SDL_GPU_LOADOP_CLEAR;
		colorTargetInfo.store_op = SDL_GPU_STOREOP_STORE;
		GPURenderPass renderPass{ cmd, &colorTargetInfo, 1 };
		if (!renderPass) {
			return;
		}
		renderPass.BindGraphicsPipeline(*graphicsPipeline);
		renderPass.BindVertexStorageBuffers(0, *spritesBuffer);
		renderPass.BindTextureSampler(0, { *spritesTexture, *texSampler });
		renderPass.DrawPrimitives(SPRITE_COUNT * 6, 1, 0, 0);
	};
	sdlfx.Run();
}


void AppContext::Cleanup() {
	gpuDevice->WaitIdle();
}

int main() {
	try {
		AppContext app;
		if (app.Init()) {
			app.Run();
		}
		app.Cleanup();
	}
	catch (const std::exception& e) {
		SDL_LogError(0, "App error! '%s'", e.what());
		return -1;
	}
	return 0;
}